#pragma once

//
// csv file parser
//
#include "string.h"
#include <vector>
#include <string>
#include <map>
#include <list>

namespace anet {
	namespace utils {
		// trim right character c.
		inline static std::string TrimRight(const std::string& strSrc, char c) {
			const char* pEnd = strSrc.c_str() + strSrc.length();
			pEnd--;
			while (*pEnd == c && pEnd >= strSrc.c_str()) {
				pEnd--;
			}
			return std::string(strSrc.c_str(), pEnd + 1);
		}

		// trim left character c.
		inline static std::string TrimLeft(const std::string& strSrc, char c) {
			const char* pStart = strSrc.c_str();
			while (*pStart == c && (pStart < strSrc.c_str() + strSrc.length())) {
				pStart++;
			}
			return std::string(pStart, strSrc.c_str() + strSrc.length());
		}

		// trim left and right space.
		inline static std::string TrimSpace(const std::string& str) {
			std::string strLine = TrimLeft(str, '\n');
			strLine = TrimLeft(strLine, '\r');
			strLine = TrimLeft(strLine, '\t');
			strLine = TrimLeft(strLine, ' ');

			strLine = TrimRight(strLine, '\n');
			strLine = TrimRight(strLine, '\r');
			strLine = TrimRight(strLine, '\t');
			strLine = TrimRight(strLine, ' ');
			return strLine;
		}

		// split strSrc with strDelim, return from vecRet.
		inline static void StrSplits(const std::string& strSrc,
			const std::string& strDelim, std::vector<std::string>& vecRet
		) {
			const char* pszNextData = strSrc.c_str();
			const char* pszPos = nullptr;
			while ((pszPos = strstr(pszNextData, strDelim.c_str())) != nullptr) {
				const auto size = (unsigned int)(pszPos - pszNextData);
				vecRet.push_back(TrimSpace(std::string(pszNextData, size)));
				pszNextData = pszPos + 1;
			}

			/* push last substring only if not empty*/
			if (pszNextData) {
				std::string strData = TrimRight(pszNextData, ' ');
				if (!strData.empty()) {
					vecRet.push_back(TrimSpace(strData));
				}
			}
		}

		class CCSVParser {
			typedef std::vector<std::string> CFieldsVec;
			typedef std::vector<CFieldsVec> CLinesVec;
			typedef std::map<std::string, size_t> CField2Index;

		public:
			CCSVParser();
			virtual ~CCSVParser();

		public:
			// all integral type such as int, char, short and so on.
			template <typename T>
			static typename std::enable_if<std::is_integral<T>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stoll(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stoll transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// bool template.
			static bool toUserType(const std::string& strVal) {
				if (strVal == "true") {
					return true;
				} else if (strVal == "false") {
					return false;
				} else {
					if (strVal.size() > 0) {
						switch (strVal[0]) {
						case 't': case 'T': case '1':
							return true;
						case 'f': case 'F': case '0':
							return false;
						default:
							return std::stoi(strVal) > 0;
						}
					} else {
						return false;
					}
				}
			}

			// unsigned long long.
			template <typename T>
			static typename std::enable_if<std::is_same<T, unsigned long long>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stoull(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stoull transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// float type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, float>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stof(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stof transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// double type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, double>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stod(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stod transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// std::string type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, std::string>::value, T>::type
				toUserType(const std::string& strVal) {
				return strVal;
			}

		public:
			bool               Load(const char* filePath, bool head = true);
			size_t             GetLineCount() const;
			size_t             GetFieldCount(size_t nLine) const;
			const std::string& GetField(size_t line, size_t field) const;

			// transfer to all kinds of type.
			const std::string& GetField(size_t line, const char* field) const;
			long long          GetFieldInt64(size_t line, const char* field) const;
			int                GetFieldInt(size_t line, const char* field) const;
			unsigned int       GetFieldUint(size_t line, const char* field) const;
			double             GetFiledFloat(size_t line, const char* field) const;
			bool               GetFieldBool(size_t line, const char* field) const;

			// get vector of T.
			template <class T>
			bool GetFieldList(size_t line, const char* field, std::vector<T> &vec) const {
				const auto &v = GetField(line, field);
				if (&v == &m_staticStr) {
					return false;
				}

				// build vector.
				std::vector<std::string> res;
				StrSplits(v, ",", vec);
				for (int i = 0;i < res.size();i++) {
					vec.push_back(toUserType<T>(TrimSpace(res[i])));
				}
				return true;
			}

			// get list of T.
			template <class T>
			bool GetFieldList(size_t line, const char* field, std::list<T>& list) const {
				const auto& v = GetField(line, field);
				if (&v == &m_staticStr) {
					return false;
				}

				// build list.
				std::vector<std::string> vec;
				StrSplits(v, ",", vec);
				for (const auto& e : vec) {
					list.push_back(toUserType<T>(TrimSpace(e)));
				}
				return true;
			}

			// get map of T.
			template<class key, class value>
			bool GetFieldMap(size_t line, const char* field, std::map<key, value>& map) const {
				const auto& v = GetField(line, field);
				if (&v == &m_staticStr) {
					return false;
				}

				// build map.
				std::vector<std::string> vec;
				StrSplits(v, ";", vec);
				for (size_t i = 0; i < vec.size(); i++) {
					std::vector<std::string> tmp;
					StrSplits(vec[i], ":", tmp);
					if (tmp.size() >= 2) {
						map[toUserType<key>(TrimSpace(tmp[0]))] = toUserType<value>(TrimSpace(tmp[1]));
					}
				}
				return true;
			}

			// get field value.
			const std::string& GetFieldWithoutError(size_t line, const char* field) const;
			const std::string& GetField(size_t line, size_t field);
			std::string        GetLine(size_t line) const;

			// check field.
			bool               HasLine(size_t line) const;
			bool               HasField(size_t line, size_t field) const;
			bool               HasField(size_t line, const char* field);
			static const std::string& GetStaticString() { 
				return m_staticStr; 
			}
			void  Write(const char* pszCsvFile);

		protected:
			// parse a line.
			void parseCSVLine(const std::string& line, std::vector<std::string> &fields);
			void parseLine(std::string& strLine, bool first = false);

		private:
			CLinesVec          m_vecLines;
			CField2Index       m_field2index;
			static std::string m_staticStr;
		};
	}
}